package cn.virens.common.project.generator.database.abstr;

import cn.virens.common.project.generator.database.beetl.BeetlFtFormat;
import cn.virens.common.project.generator.database.beetl.BeetlFtKeyword;
import cn.virens.common.project.generator.database.config.GlobalConfig;
import cn.virens.common.project.generator.database.datasource.DataSourceConfig;
import cn.virens.common.project.generator.database.datasource.info.FieldInfo;
import cn.virens.common.project.generator.database.datasource.info.TableInfo;
import cn.virens.common.util.core.Assert;
import cn.virens.common.util.core.stream.StreamUtil;
import cn.virens.common.util.exception.APIException;
import cn.virens.common.util.exception.ExceptionUtil;
import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.ResourceLoader;
import org.beetl.core.resource.ClasspathResourceLoader;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.setting.Setting;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;

public abstract class AbstractGenerator {
    private static final String sql1 = "SELECT t.* FROM information_schema.`TABLES` AS t WHERE t.TABLE_SCHEMA = '%s' AND t.TABLE_NAME LIKE '%s'";
    private static final String sql2 = "SELECT t.* FROM information_schema.`COLUMNS` AS t WHERE t.TABLE_SCHEMA = '%s' AND t.TABLE_NAME LIKE '%s' ORDER BY t.ORDINAL_POSITION";

    protected final DataSourceConfig sourceConfig;
    protected final GlobalConfig globalConfig;

    protected ResourceLoader<String> resourceLoader;
    protected Configuration configuration;
    protected GroupTemplate template;

    public AbstractGenerator(Setting setting) throws APIException {
        this.sourceConfig = new DataSourceConfig(setting);
        this.globalConfig = new GlobalConfig(setting);
    }

    /**
     * 初始化 Beetl 模版引擎
     *
     * @throws APIException
     * @throws IOException
     */
    @BeforeEach
    protected void builderInitialize() throws IOException {
        Properties properties = new Properties();
        properties.put("DELIMITER_STATEMENT_END", "/>");
        properties.put("DELIMITER_PLACEHOLDER_END", "}}");
        properties.put("DELIMITER_STATEMENT_START", "<tpl");
        properties.put("DELIMITER_PLACEHOLDER_START", "${{");

        properties.put("FT.format", BeetlFtFormat.class.getName());
        properties.put("FT.keyword", BeetlFtKeyword.class.getName());

        this.configuration = new Configuration(properties);
        this.resourceLoader = new ClasspathResourceLoader();
        this.template = new GroupTemplate(resourceLoader, configuration);
    }

    /**
     * 代码构建生成器
     *
     * @throws APIException
     */
    @Test
    public void builderWithRunBuildFileToTarget() throws Exception {
        StreamUtil.stream(executeQueryTable(sqlTable(globalConfig))).forEach(t -> {
            List<FieldInfo> fieldInfos = executeQueryColumn(sqlColumn(globalConfig, t));

            findFileOutStream().filter(AbstractGeneratorFileOut::enable).forEach(r -> {
                r.render(t, fieldInfos);
            });
        });
    }

    /**
     * 各种生成器所在的包路径
     */
    protected String fileout() throws APIException {
        return "cn.virens.common.project.generator.database.fileout";
    }

    /**
     * 查找实现模版文件输出的类
     */
    private Stream<AbstractGeneratorFileOut> findFileOutStream() throws APIException {
        var generators = ClassUtil.scanPackage(fileout(), AbstractGeneratorFileOut.class::isAssignableFrom);

        return StreamUtil.stream(generators).map(this::newInstance);
    }

    /**
     * 构造文件输出对象
     */
    private AbstractGeneratorFileOut newInstance(Class<?> clazz) throws APIException {
        return (AbstractGeneratorFileOut) ConstructorUtil.newInstance(clazz, this);
    }

    /**
     * 数据库字段查询
     */
    protected List<FieldInfo> executeQueryColumn(String sql) throws APIException {
        return ExceptionUtil.e1(() -> f(sourceConfig.executeQuery(sql, r -> {
            FieldInfo answer = new FieldInfo();
            answer.setCdefault(r.getString("COLUMN_DEFAULT"));
            answer.setComment(r.getString("COLUMN_COMMENT"));
            answer.setOrder(r.getString("ORDINAL_POSITION"));
            answer.setName(r.getString("COLUMN_NAME"));
            answer.setType(r.getString("DATA_TYPE"));
            answer.setKey(r.getString("COLUMN_KEY"));

            return answer;
        })));
    }

    /**
     * 数据库表查询
     */
    protected List<TableInfo> executeQueryTable(String sql) throws APIException {
        return ExceptionUtil.e1(() -> sourceConfig.executeQuery(sql, r -> {
            TableInfo answer = new TableInfo();
            answer.setComment(r.getString("TABLE_COMMENT"));
            answer.setSchema(r.getString("TABLE_SCHEMA"));
            answer.setName(r.getString("TABLE_NAME"));

            return answer;
        }));
    }

    /**
     * 过滤BaseMode已经存在的字段
     */
    private List<FieldInfo> f(List<FieldInfo> fieldInfoList) throws APIException {
        return CollUtil.filter(Assert.isNull(fieldInfoList), a -> {
            if ("create_time".equals(a.getName())) return false;
            if ("update_time".equals(a.getName())) return false;
            if ("id".equals(a.getName())) return false;

            return true;
        });
    }

    protected static String sqlTable(GlobalConfig globalConfig) throws APIException {
        return String.format(sql1, globalConfig.getSchema(), globalConfig.getTable());
    }

    protected static String sqlColumn(GlobalConfig globalConfig, TableInfo table) throws APIException {
        return String.format(sql2, globalConfig.getSchema(), table.getName());
    }
}
